#========================================================================
# Copyright (c) 2007, Metaweb Technologies, Inc.
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#	 * Redistributions of source code must retain the above copyright
#	   notice, this list of conditions and the following disclaimer.
#	 * Redistributions in binary form must reproduce the above
#	   copyright notice, this list of conditions and the following
#	   disclaimer in the documentation and/or other materials provided
#	   with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY METAWEB TECHNOLOGIES ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL METAWEB TECHNOLOGIES BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# ========================================================================
#
# This is the full "metaweb.py" module from the Metaweb API documentation
#
# In the documentation, each function is presented as a separate
# example.  This is the whole file.
#
# If you find any errors or have suggestions for improving this module,
# send them to the Freebase developers mailing list: developers@freebase.com
# You can subscribe to the mailing list at http://lists.freebase.com/
#

import urllib		# URL encoding
import urllib2	   # Higher-level URL content fetching
try:
	import json
except:
	import simplejson as json

import httplib
##	import pprint; pprint.pprint(response, indent = 1)

#
# When experimenting, use the sandbox.freebase.com service.
# Every Monday, sandbox.freebase.com is erased and it is updated
# with a fresh copy of data from www.freebase.com.  This makes
# it an ideal place to experiment.
#
host = 'sandbox.freebase.com'			  # The Metaweb host
readservice = '/api/service/mqlread'   # Path to mqlread service
loginservice = '/api/account/login'	 # Path to login service
writeservice = '/api/service/mqlwrite'  # Path to mqlwrite service
uploadservice = '/api/service/upload'   # Path to upload service
				 
# If anything goes wrong when talking to a Metaweb service, we raise MQLError.
class MQLError(Exception):
	def __init__(self, value):	 # This is the exception constructor method
		self.value = value
	def __str__(self):			 # Convert error object to a string
		return repr(self.value)

# Submit the MQL query q and return the result as a Python object.
# If authentication credentials are supplied, use them in a cookie.
# Raises MQLError if the query was invalid. Raises urllib2.HTTPError if
# mqlread returns an HTTP status code other than 200 (which should not happen).
def read(q, credentials=None):
	# Put the query in an envelope
	env = {'qname':{'query':q}}
	# JSON serialize and URL encode the envelope and the query parameter
	args = urllib.urlencode({'queries':json.dumps(env)})
	# Build the URL and create a Request object for it
	url = 'http://%s%s?%s' % (host, readservice, args)
	req = urllib2.Request(url)

	# Send our authentication credentials, if any, as a cookie.
	# The need for mqlread authentication is a temporary restriction.
	if credentials: 
		req.add_header('Cookie', credentials)

	# Now upen the URL and and parse its JSON content
	f = urllib2.urlopen(req)		# Open the URL
	response = json.load(f)   # Parse JSON response to an object
	inner = response['qname']	   # Open outer envelope; get inner envelope

	# If anything was wrong with the invocation, mqlread will return an HTTP
	# error, and the code above with raise urllib2.HTTPError.
	# If anything was wrong with the query, we won't get an HTTP error, but
	# will get an error status code in the response envelope.  In this case
	# we raise our own MQLError exception.
	if not inner['code'].startswith('/api/status/ok'):
		error = inner['messages'][0]
		raise MQLError('%s: %s' % (error['code'], error['message']))

	# If there was no error, then just return the result from the envelope
	return inner['result'];

# Submit the MQL query q and return the result as a Python object
# This function behaves like read() above, but uses cursors so that
# it works even for very large result sets
def readall(q, credentials=None):
	# This is the start of the mqlread URL.
	# We just need to append the envelope to it
	urlprefix = 'http://%s%s?queries=' % (host, readservice)

	# The query and most of the envelope are constant. We just need to append
	# the encoded cursor value and some closing braces to this prefix string
	jsonq = json.dumps(q);
	envelopeprefix = urllib.quote_plus('{"q0":{"query":'+jsonq+',"cursor":')
	
	cursor = 'true'   # This is the initial value of the cursor
	results = []	  # We accumulate results in this array

	# Loop until mqlread tells us there are no more results
	while cursor:
		# append the cursor and the closing braces to the envelope
		envelope = envelopeprefix + urllib.quote_plus(cursor + '}}')
		# append the envelope to the URL
		url = urlprefix + envelope

		# Begin an HTTP request for the URL
		req = urllib2.Request(url)

		# Send our authentication credentials, if any, as a cookie.
		# The need for mqlread authentication is a temporary restriction.
		if credentials: 
			req.add_header('Cookie', credentials)

		# Read and parse the URL contents
		f = urllib2.urlopen(req)		  # Open URL
		response = json.load(f)	 # Parse JSON response
		inner = response['q0']			# Get inner envelope from outer

		# Raise a MQLError if there were errors
		if not inner['code'].startswith('/api/status/ok'):
			error = inner['messages'][0]
			raise MQLError('%s: %s' % (error['code'], error['message']))

		# Append this batch of results to the main array of results.
		results.extend(inner['result']);

		# Finally, get the new value of the cursor for the next iteration
		cursor = inner['cursor']
		if cursor:						# If it is not false, put it
			cursor = '"' + cursor + '"'   #  in quotes as a JSON string

	# Now that we're done with the loop, return the results array
	pprint.pprint(results)
	return results

# Submit the specified username and password to the Metaweb login service.
# Return opaque authentication credentials on success. 
# Raise MQLError on failure.
def login(username, password):
	# Establish a connection to the server and make a request.
	# Note that we use the low-level httplib library instead of urllib2.
	# This allows us to manage cookies explicitly.
	conn = httplib.HTTPConnection(host)
	conn.request('POST',				   # POST the request
				 loginservice,			 # The URL path /api/account/login
				 # The body of the request: encoded username/password
				 urllib.urlencode({'username':username, 'password':password}),
				 # This header specifies how the body of the post is encoded.
				 {'Content-type': 'application/x-www-form-urlencoded'})

	# Get the response from the server
	response = conn.getresponse()

	if response.status == 200:  # We get HTTP 200 OK even if login fails
		# Parse response body and raise a MQLError if login failed
		body = json.loads(response.read())
		if not body['code'].startswith('/api/status/ok'):
			error = body['messages'][0]
			raise MQLError('%s: %s' % (error['code'], error['message']))

		# Otherwise return cookies to serve as authentication credentials.
		# The set-cookie header holds one or more cookie specifications,
		# separated by commas. Each specification is a name, an equal
		# sign, a value, and one or more trailing clauses that consist
		# of a semicolon and some metadata.  We don't care about the
		# metadata. We just want to return a comma-separated list of
		# name=value pairs.
		cookies = response.getheader('set-cookie').split(',')
		return ';'.join([c[0:c.index(';')] for c in cookies])
	else:					  # This should never happen
		raise MQLError('HTTP Error: %d %s' % (response.status,response.reason))


# Submit the MQL write q and return the result as a Python object.
# Authentication credentials are required, obtained from login()
# Raises MQLError if the query was invalid. Raises urllib2.HTTPError if
# mqlwrite returns an HTTP status code other than 200
def write(query, credentials):
	# We're requesting this URL
	req = urllib2.Request('http://%s%s' % (host, writeservice))
	# Send our authentication credentials as a cookie
	req.add_header('Cookie', credentials)
	# This custom header is required and guards against XSS attacks
	req.add_header('X-Metaweb-Request', 'True')
	# The body of the POST request is encoded URL parameters
	req.add_header('Content-type', 'application/x-www-form-urlencoded')
	# Wrap the query object in a query envelope
	envelope = {'qname': {'query': query}}
	# JSON encode the envelope
	encoded = json.dumps(envelope)
	print encoded
	# Use the encoded envelope as the value of the q parameter in the body
	# of the request.  Specifying a body automatically makes this a POST.
	req.add_data(urllib.urlencode({'queries':encoded}))

	# Now do the POST
	f = urllib2.urlopen(req)
	response = json.load(f)   # Parse HTTP response as JSON
	print response
	inner = response['qname']	   # Open outer envelope; get inner envelope

	# If anything was wrong with the invocation, mqlwrite will return an HTTP
	# error, and the code above with raise urllib2.HTTPError.
	# If anything was wrong with the query, we will get an error status code
	# in the response envelope.
	# we raise our own MQLError exception.
	if not inner['code'].startswith('/api/status/ok'):
		error = inner['messages'][0]
		raise MQLError('%s: %s' % (error['code'], error['message']))

	# If there was no error, then just return the result from the envelope
	return inner['result']

# Upload the specified content (and give it the specified type).
# Return the guid of the /type/content object that represents it.
# The returned guid can be used to retrieve the content with /api/trans/raw.
def upload(content, type, credentials):
	# This is the URL we POST content to
	url = 'http://%s%s'%(host,uploadservice)
	# Build the HTTP request
	req = urllib2.Request(url, content)		 # URL and content to POST
	req.add_header('Content-Type', type)		# Content type header
	req.add_header('Cookie', credentials)	   # Authentication header
	req.add_header('X-Metaweb-Request', 'True') # Guard against XSS attacks
	f = urllib2.urlopen(req)				# POST the request
	response = json.load(f)		   # Parse the response
	if not response['code'].startswith('/api/status/ok'):
		error = response['messages'][0]
		raise MQLError('%s: %s' % (error['code'], error['message']))
	return response['result']['id']		 # Extract and return content id

if __name__ == '__main__':
	import pprint

	query = [{
		'type': '/film/film',
		'name~=' : 'The Godfather',
		'name' : None,
		'/common/topic/image' : [{}],
		'/common/topic/article' : [{}],
		'/common/topic/webpage' : [{}],
		"key" : [
			{
				u'namespace': u'/wikipedia/en',
				u'type': u'/type/key',
				u'value': None,
			},
		],
		"imdb_id" : None,
		"id" : None,
		"initial_release_date" : None,
		"sort" : "-initial_release_date",
		"netflix_id" : None,
		"guid" : None,
	}]

	credentials = login("dpjanes", "xxxxxxx")
	resultds = read(query, credentials)

	pprint.pprint(resultds)

